#include "Sky.h"
#include <toadlet/tadpole/plugins/SkyDomeMeshCreator.h>

Sky::Sky(Scene *scene,int cloudSize,const Vector4 &skyColor,const Vector4 &fadeColor):Node(scene)
{
	Sphere sphere(Vector3(0,0,0),512);
	Vector3 lightDir(1,-1,0.5);
	bool advanced=false; // Use realtime bumpmapping, or precalculated

	Logger::alert("Allocating Sky resources");

	SkyDomeMeshCreator::ptr skyDomeCreator=new SkyDomeMeshCreator(mEngine);
 	int numSegments=16,numRings=16;
	VertexBuffer::ptr vertexBuffer=mEngine->getBufferManager()->createVertexBuffer(Buffer::Usage_BIT_STREAM,Buffer::Access_READ_WRITE,mEngine->getVertexFormats().POSITION_COLOR_TEX_COORD,skyDomeCreator->getSkyDomeVertexCount(numSegments,numRings));
	IndexBuffer::ptr indexBuffer=mEngine->getBufferManager()->createIndexBuffer(Buffer::Usage_BIT_STATIC,Buffer::Access_BIT_WRITE,IndexBuffer::IndexFormat_UINT16,skyDomeCreator->getSkyDomeIndexCount(numSegments,numRings));

	TextureFormat::ptr cloudFormat(new TextureFormat(TextureFormat::Dimension_D2,TextureFormat::Format_RGBA_8,cloudSize,cloudSize,1,0));
	tbyte *cloudData=createCloud(cloudFormat,16,9,0.45,0.000025,0.75);
	TextureFormat::ptr bumpFormat(new TextureFormat(TextureFormat::Dimension_D2,TextureFormat::Format_RGB_8,cloudSize,cloudSize,1,0));
	tbyte *bumpData=createBump(bumpFormat,cloudData,-1,-1,-32,4); // To debug any bump issues, try disabling fadeStage, make bump/cloud stage modulate, add a rotating sun, and use a zscale of 1

	Material::ptr material;
	if(advanced){
		Texture::ptr cloudTexture=mEngine->getTextureManager()->createTexture(cloudFormat,cloudData);
		Texture::ptr bumpTexture=mEngine->getTextureManager()->createTexture(bumpFormat,bumpData);

		TextureFormat::ptr fadeFormat(new TextureFormat(TextureFormat::Dimension_D2,TextureFormat::Format_RGBA_8,cloudSize/2,cloudSize/2,1,0));
		tbyte *fadeData=createFade(fadeFormat,Vector4(fadeColor.x,fadeColor.y,fadeColor.z,0),fadeColor,1.1,0.01);
		Texture::ptr fadeTexture=mEngine->getTextureManager()->createTexture(fadeFormat,fadeData);
		delete[] fadeData;

		material=mEngine->getMaterialManager()->createMaterial();
		if(mEngine->hasShader(Shader::ShaderType_VERTEX) && mEngine->hasShader(Shader::ShaderType_FRAGMENT)){
			RenderPath::ptr shaderPath=material->addPath();
			RenderPass::ptr pass=shaderPath->addPass();

			pass->setBlendState(BlendState());
			pass->setDepthState(DepthState(DepthState::DepthTest_ALWAYS,false));
			pass->setRasterizerState(RasterizerState());

			String profiles[]={
				"glsl",
				"hlsl"
			};

			String vertexCodes[]={
				"attribute vec4 POSITION;\n"
				"attribute vec4 COLOR;\n"
				"attribute vec2 TEXCOORD0;\n"
				"varying vec4 color;\n"
				"varying vec2 texCoord0;\n"
				"varying vec2 texCoord1;\n"

				"uniform mat4 modelViewProjectionMatrix;\n"
				"uniform mat4 textureMatrix;\n"

				"void main(){\n"
					"gl_Position=modelViewProjectionMatrix * POSITION;\n"
					"color=COLOR;\n"
					"texCoord0=(textureMatrix * vec4(TEXCOORD0,0.0,1.0)).xy;\n "
					"texCoord1=TEXCOORD0;\n"
				"}",



				"struct VIN{\n"
					"float4 position : POSITION;\n"
					"float4 color : COLOR;\n"
					"float2 texCoord: TEXCOORD0;\n"
				"};\n"
				"struct VOUT{\n"
					"float4 position : SV_POSITION;\n"
					"float4 color : COLOR;\n"
					"float2 texCoord0: TEXCOORD0;\n"
					"float2 texCoord1: TEXCOORD1;\n"
				"};\n"

				"float4x4 modelViewProjectionMatrix;\n"
				"float4x4 textureMatrix;\n"

				"VOUT main(VIN vin){\n"
					"VOUT vout;\n"
					"vout.position=mul(modelViewProjectionMatrix,vin.position);\n"
					"vout.color=vin.color;\n"
					"vout.texCoord0=mul(textureMatrix,float4(vin.texCoord,0.0,1.0));\n "
					"vout.texCoord1=vin.texCoord;\n "
					"return vout;\n"
				"}"
			};

			String fragmentCodes[]={\
				"#if defined(GL_ES)\n"
					"precision mediump float;\n"
				"#endif\n"
				
				"varying vec4 color;\n"
				"varying vec2 texCoord0;\n"
				"varying vec2 texCoord1;\n"

				"uniform vec4 skyColor;\n"
				"uniform sampler2D bumpTex,cloudTex,fadeTex;\n"

				"void main(){\n"
					"vec4 bump=texture2D(bumpTex,texCoord0);\n"
					"vec4 cloud=texture2D(cloudTex,texCoord0);\n"
					"vec4 fade=texture2D(fadeTex,texCoord1);\n"
					"vec4 color=vec4(dot((color.xyz-0.5)*2.0,(bump.xyz-0.5)*2.0));\n"
					"color=vec4(color.xyz+cloud.xyz,cloud.a);\n"
					"color=mix(skyColor,color,cloud.w);\n"
					"color=mix(fade,color,fade.w);\n"
					"gl_FragColor = color;\n"
				"}",



				"struct PIN{\n"
					"float4 position: SV_POSITION;\n"
					"float4 color: COLOR;\n"
					"float2 texCoord0: TEXCOORD0;\n"
					"float2 texCoord1: TEXCOORD1;\n"
				"};\n"

				"float4 skyColor;\n"
				"Texture2D bumpTex,cloudTex,fadeTex;\n"
				"SamplerState bumpSamp,cloudSamp,fadeSamp;\n"

				"float4 main(PIN pin): SV_TARGET{\n`"
					"float4 bump=bumpTex.Sample(bumpSamp,pin.texCoord0);\n"
					"float4 cloud=cloudTex.Sample(cloudSamp,pin.texCoord0);\n"
					"float4 fade=fadeTex.Sample(fadeSamp,pin.texCoord1);\n"
					"float4 color=dot((pin.color.xyz-0.5)*2,(bump.xyz-0.5)*2);\n"
					"color=float4(color.xyz+cloud.xyz,cloud.a);\n"
					"color=lerp(skyColor,color,cloud.w);\n"
					"color=lerp(fade,color,fade.w);\n"
					"return color;\n"
				"}"
			};

			Shader::ptr vertexShader=mEngine->getShaderManager()->createShader(Shader::ShaderType_VERTEX,profiles,vertexCodes,2);
			pass->setShader(Shader::ShaderType_VERTEX,vertexShader);
			Shader::ptr fragmentShader=mEngine->getShaderManager()->createShader(Shader::ShaderType_FRAGMENT,profiles,fragmentCodes,2);
			pass->setShader(Shader::ShaderType_FRAGMENT,fragmentShader);

			RenderVariableSet::ptr variables=pass->makeVariables();
			variables->addVariable("modelViewProjectionMatrix",RenderVariable::ptr(new MVPMatrixVariable()),Material::Scope_RENDERABLE);
			variables->addVariable("textureMatrix",RenderVariable::ptr(new TextureMatrixVariable(Shader::ShaderType_VERTEX,0)),Material::Scope_MATERIAL);
			variables->addVariable("skyColor",RenderVariable::ptr(new Vector4Variable(skyColor)),Material::Scope_MATERIAL);

			variables->addTexture("bumpTex",bumpTexture,"bumpSamp",SamplerState(),TextureState());
			variables->addTexture("cloudTex",cloudTexture,"cloudSamp",SamplerState(),TextureState());
			variables->addTexture("fadeTex",fadeTexture,"fadeSamp",SamplerState(),TextureState());
			mShaderAccessor=Matrix4x4Accessor::ptr(new TextureStateMatrix4x4Accessor(pass,0));
		}

		if(mEngine->hasFixed(Shader::ShaderType_VERTEX) && mEngine->hasFixed(Shader::ShaderType_FRAGMENT)){
			RenderPath::ptr fixedPath=material->addPath();
			RenderPass::ptr pass=fixedPath->addPass();

			pass->setBlendState(BlendState());
			pass->setDepthState(DepthState(DepthState::DepthTest_ALWAYS,false));
			pass->setRasterizerState(RasterizerState());
			pass->setMaterialState(MaterialState(false,true));

			int state=0;
			TextureState bumpState;
			bumpState.colorOperation=TextureState::Operation_DOTPRODUCT;
			bumpState.colorSource1=TextureState::Source_PREVIOUS;
			bumpState.colorSource2=TextureState::Source_TEXTURE;
			pass->setTexture(Shader::ShaderType_FRAGMENT,state,bumpTexture,SamplerState(),bumpState);
			mBumpAccessor=Matrix4x4Accessor::ptr(new TextureStateMatrix4x4Accessor(pass,state++));
/*
			TextureState cloudState;
			cloudState.colorOperation=TextureState::Operation_ADD;
			cloudState.colorSource1=TextureState::Source_PREVIOUS;
			cloudState.colorSource2=TextureState::Source_TEXTURE;
			cloudState.alphaOperation=TextureState::Operation_REPLACE;
			cloudState.alphaSource1=TextureState::Source_TEXTURE;
			pass->setTexture(Shader::ShaderType_FRAGMENT,state,cloudTexture,SamplerState(),cloudState);
			mCloudAccessor=Matrix4x4Accessor::ptr(new TextureStateMatrix4x4Accessor(pass,state++));

			TextureState colorState;
			colorState.constantColor.set(skyColor);
			colorState.colorOperation=TextureState::Operation_ALPHABLEND;
			colorState.colorSource1=TextureState::Source_PREVIOUS;
			colorState.colorSource2=TextureState::Source_CONSTANT_COLOR;
			colorState.colorSource3=TextureState::Source_PREVIOUS;
			colorState.alphaOperation=TextureState::Operation_REPLACE;
			colorState.alphaSource1=TextureState::Source_PREVIOUS;
			pass->setTexture(Shader::ShaderType_FRAGMENT,state,cloudTexture,SamplerState(),colorState); // Need a texture for this state to function on OpenGL currently
			mColorAccessor=Matrix4x4Accessor::ptr(new TextureStateMatrix4x4Accessor(pass,state++));

			TextureState fadeState;
			fadeState.colorOperation=TextureState::Operation_ALPHABLEND;
			fadeState.colorSource1=TextureState::Source_PREVIOUS;
			fadeState.colorSource2=TextureState::Source_TEXTURE;
			fadeState.colorSource3=TextureState::Source_TEXTURE;
			fadeState.alphaOperation=TextureState::Operation_REPLACE;
			fadeState.alphaSource1=TextureState::Source_TEXTURE;
			pass->setTexture(Shader::ShaderType_FRAGMENT,state,fadeTexture,SamplerState(),fadeState);
			mFadeAccessor=Matrix4x4Accessor::ptr(new TextureStateMatrix4x4Accessor(pass,state++));
*/
		}

		material->compile();
	}
	else{
		TextureFormat::ptr compositeFormat(new TextureFormat(TextureFormat::Dimension_D2,TextureFormat::Format_RGB_8,cloudSize,cloudSize,1,0));
		tbyte *compositeData=createComposite(compositeFormat,cloudData,bumpData,lightDir,skyColor);
		Texture::ptr compositeTexture=mEngine->getTextureManager()->createTexture(compositeFormat,compositeData);
		delete[] compositeData;

		material=mEngine->createSkyBoxMaterial(compositeTexture,false);
		mCompositeAccessor=Matrix4x4Accessor::ptr(new TextureStateMatrix4x4Accessor(material->getPass(),0));
		material->getPass()->setBlendState(BlendState::Combination_ALPHA);
		material->getPass()->setMaterialState(MaterialState(false,true));
	}
	mSkyMaterial=material;
	mSkyMaterial->setLayer(-2);

	delete[] cloudData;
	delete[] bumpData;

	Mesh::ptr mesh=skyDomeCreator->createSkyDomeMesh(vertexBuffer,indexBuffer,sphere,numSegments,numRings,0.35);
	Transform transform;
	transform.setScale(1,1,0.5f);
	mesh->setTransform(transform);
	mesh->getSubMesh(0)->material=mSkyMaterial;

	mSkyDome=new MeshComponent(mEngine);
	mSkyDome->setMesh(mesh);
	attach(mSkyDome);

	TextureFormat::ptr glowFormat(new TextureFormat(TextureFormat::Dimension_D2,TextureFormat::Format_L_8,128,128,1,0));
	tbyte *glowData=createGlow(glowFormat);
	Texture::ptr glowTexture=mEngine->getTextureManager()->createTexture(glowFormat,glowData);
	delete[] glowData;

	Material::ptr sunMaterial=mEngine->createDiffuseMaterial(glowTexture);
	sunMaterial->setLayer(-1);
	sunMaterial->setSort(Material::SortType_MATERIAL);
	sunMaterial->getPass()->setBlendState(BlendState::Combination_COLOR_ADDITIVE);
	sunMaterial->getPass()->setMaterialState(MaterialState(false));
	sunMaterial->getPass()->setDepthState(DepthState(DepthState::DepthTest_ALWAYS,false));
	sunMaterial->compile();

	mSunNode=new Node(mScene);
	{
		mSun=new SpriteComponent(mEngine);
		mSun->setMaterial(sunMaterial);
		mSunNode->attach(mSun);
	}
	mSunNode->setScale(128,128,128);
	attach(mSunNode);

	Logger::alert("Done allocating Sky resources");

	mLightNode=new Node(mScene);
	{
		mLight=new LightComponent();
		LightState lightState;
		lightState.diffuseColor.set(Colors::WHITE);
		lightState.specularColor.set(Colors::WHITE/4);
		mLight->setLightState(lightState);
		mLightNode->attach(mLight);
	}
	attach(mLightNode);

	Math::normalize(lightDir);
	updateLightDirection(lightDir);
}

void Sky::destroy(){
	if(mSkyMaterial!=NULL){
		mSkyMaterial->destroy();
		mSkyMaterial=NULL;
	}

	Node::destroy();
}

void Sky::updateLightDirection(const Vector3 &lightDir){
	LightState state;
	mLight->getLightState(state);
	state.direction=-lightDir;
	mLight->setLightState(state);

	mSunNode->setTranslate(lightDir*256);

	VertexBuffer *buffer=mSkyDome->getMesh()->getStaticVertexData()->getVertexBuffer(0);
	VertexBufferAccessor vba(buffer,Buffer::Access_READ_WRITE);
	int ip=buffer->getVertexFormat()->findElement(VertexFormat::Semantic_POSITION);
	int ic=buffer->getVertexFormat()->findElement(VertexFormat::Semantic_COLOR);
	Vector3 pos,dir;
	int i;
	for(i=0;i<vba.getSize();++i){
		vba.get3(i,ip,pos);
		Math::normalize(pos);
		if(mShaderAccessor!=NULL || mBumpAccessor!=NULL){
			pos.z=0;
			Math::sub(dir,pos,lightDir);
			Math::normalize(dir);
			vba.setRGBA(i,ic,Vector4(dir.x*0.5+Math::HALF,dir.y*0.5+Math::HALF,dir.z*0.5+Math::HALF,Math::ONE).getRGBA());
		}
		else{
			scalar alpha=Math::clamp(0,Math::ONE,(pos.z-0)/0.75);
			vba.setRGBA(i,ic,Vector4(Math::ONE,Math::ONE,Math::ONE,alpha).getRGBA());
		}
	}
	vba.unlock();
}

void Sky::frameUpdate(int dt,int scope){
	Node::frameUpdate(dt,scope);

	Vector3 offset;
	Math::mul(offset,getWorldTranslate(),0.0001); // Since the sky is in the background, it's world translate will be the viewer's world translate
	offset.x+=Math::fromMilli(mScene->getTime())*0.01;
	Matrix4x4 matrix;
	Math::setMatrix4x4FromTranslate(matrix,offset);
	if(mShaderAccessor!=NULL){
		mShaderAccessor->setMatrix4x4(matrix);
	}
	if(mBumpAccessor!=NULL){
		mBumpAccessor->setMatrix4x4(matrix);
	}
	if(mCloudAccessor!=NULL){
		mCloudAccessor->setMatrix4x4(matrix);
	}
	if(mCompositeAccessor!=NULL){
		mCompositeAccessor->setMatrix4x4(matrix);
	}
}

tbyte *Sky::createCloud(TextureFormat *format,int scale,int seed,float cover,float sharpness,float brightness){
	int width=format->getWidth(),height=format->getHeight();
	tbyte *data=new tbyte[format->getDataSize()];

	Noise noise(4,scale,1,seed,256);

	int x=0,y=0;
	for(y=0;y<height;y++){
		for(x=0;x<width;x++){
			float n=(noise.tileablePerlin2((float)x/(float)width,(float)y/(float)height,1,1)*0.5 + 0.5);
			n-=cover;
			if(n<0){n=0;}
			float density=1.0-pow(sharpness,n);

			data[(y*width+x)*4+0]=brightness*255;
			data[(y*width+x)*4+1]=brightness*255;
			data[(y*width+x)*4+2]=brightness*255;
			data[(y*width+x)*4+3]=density*255;
		}
	}

	return data;
}

tbyte *Sky::createBump(TextureFormat *format,tbyte *cloudSrc,float xscale,float yscale,float zscale,int spread){
	int width=format->getWidth(),height=format->getHeight();
	uint8 *inData=cloudSrc;

	uint8 *data=new uint8[format->getDataSize()];
	uint8 ip,xp,yp; 
	int x=0,y=0;
	for(y=0;y<height;y++){
		for(x=0;x<width;x++){
			int sx=(x+1)%width;
			int sy=(y+1)%height;

			ip=inData[(y*width+x)*4+3];
			xp=inData[(y*width+sx)*4+3];
			yp=inData[(sy*width+x)*4+3];

			Vector3 xv(xscale,0,xp-ip);Math::normalize(xv);
			Vector3 yv(0,yscale,yp-ip);Math::normalize(yv);
			Vector3 zv;Math::cross(zv,xv,yv);
			zv.z*=zscale;Math::normalize(zv);

			data[(y*width+x)*3+0]=(zv.x/2+0.5)*255;
			data[(y*width+x)*3+1]=(zv.y/2+0.5)*255;
			data[(y*width+x)*3+2]=(zv.z/2+0.5)*255;
		}
	}

	uint8 *blurData=new uint8[format->getDataSize()];
	int i=0,j=0;
	for(y=0;y<height;y++){
		for(x=0;x<width;x++){
			int tx=0,ty=0,tz=0;
			for(i=-spread;i<=spread;++i){
				for(j=-spread;j<=spread;++j){
					int sx=(x+i+width)%width;
					int sy=(y+j+height)%height;

					tx+=data[(sy*width+sx)*3+0];
					ty+=data[(sy*width+sx)*3+1];
					tz+=data[(sy*width+sx)*3+2];
				}
			}
			tx/=((spread*2+1)*(spread*2+1));
			ty/=((spread*2+1)*(spread*2+1));
			tz/=((spread*2+1)*(spread*2+1));
			
			blurData[(y*width+x)*3+0]=tx;
			blurData[(y*width+x)*3+1]=ty;
			blurData[(y*width+x)*3+2]=tz;
		}
	}

	delete[] data;

	return blurData;
}

tbyte *Sky::createFade(TextureFormat *format,const Vector4 &start,const Vector4 &end,float falloff,float sharpness){
	int width=format->getWidth(),height=format->getHeight();
	uint8 *data=new uint8[format->getDataSize()];

	int x=0,y=0;
	for(y=0;y<height;y++){
		for(x=0;x<width;x++){
			float v=(1.0-Math::length(Vector2(x-width/2,y-height/2))*falloff/(width/2));
			v=1.0-pow(sharpness,v);
			if(v<0) v=0;
			if(v>1) v=1;

			uint32 color=Math::lerpColor(start.getRGBA(),end.getRGBA(),v);

			data[(y*width+x)*4+0]=(color>>24)&0xFF;
			data[(y*width+x)*4+1]=(color>>16)&0xFF;
			data[(y*width+x)*4+2]=(color>>8)&0xFF;
			data[(y*width+x)*4+3]=(color>>0)&0xFF;
		}
	}

	return data;
}

tbyte *Sky::createComposite(TextureFormat *format,tbyte *cloudData,tbyte *bumpData,const Vector3 &lightDir,const Vector4 &skyColor){
	int width=format->getWidth(),height=format->getHeight();
	uint8 *data=new uint8[format->getDataSize()];

	Vector3 dir;
	Math::neg(dir,lightDir);

	Vector3 n;
	Vector4 c,b,f;
	int x=0,y=0;
	for(y=0;y<height;y++){
		for(x=0;x<width;x++){
			tbyte br=bumpData[(y*width+x)*3+0];
			tbyte bg=bumpData[(y*width+x)*3+1];
			tbyte bb=bumpData[(y*width+x)*3+2];
			n.set(br/127.5-1.0, bg/127.5-1.0, bb/127.5-1.0);
			Math::mul(n,Math::HALF);

			tbyte cr=cloudData[(y*width+x)*4+0];
			tbyte cg=cloudData[(y*width+x)*4+1];
			tbyte cb=cloudData[(y*width+x)*4+2];
			tbyte ca=cloudData[(y*width+x)*4+3];
			c.set(cr/255.0,cg/255.0,cb/255.0,ca/255.0);

			scalar brightness=Math::clamp(0,Math::ONE,Math::dot(n,dir));
			b.set(brightness,brightness,brightness,Math::ONE);

			Math::add(f,c,b);
			if(f.x>Math::ONE) f.x=Math::ONE;
			if(f.y>Math::ONE) f.y=Math::ONE;
			if(f.z>Math::ONE) f.z=Math::ONE;
			if(f.w>Math::ONE) f.w=Math::ONE;
			Math::lerp(f,skyColor,f,c.w);

			data[(y*width+x)*3+0]=Math::clamp(0,Math::ONE,f.x)*255.0;
			data[(y*width+x)*3+1]=Math::clamp(0,Math::ONE,f.y)*255.0;
			data[(y*width+x)*3+2]=Math::clamp(0,Math::ONE,f.z)*255.0;
		}
	}

	return data;
}

tbyte *Sky::createGlow(TextureFormat *format){
	int width=format->getWidth(),height=format->getHeight();
	uint8 *data=new uint8[format->getDataSize()];

	int x=0,y=0;
	for(y=0;y<height;y++){
		for(x=0;x<width;x++){
			float v=1.0;
			v=v*(1.0-Math::length(Vector2(x-width/2,y-height/2))/(width/2));
			v=pow(v*2,3);
			v=Math::clamp(0,1,v);

			data[y*width+x]=255*v;
		}
	}

	return data;
}
